#include "widget.h"
#include "ui_widget.h"

#include <arpa/inet.h>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {
  ui->setupUi(this);
  this->setWindowTitle("TCP客户端");

  connect(&m_timer, &QTimer::timeout, this, &Widget::onSendRead);
  m_timer.setInterval(100);
}

Widget::~Widget() { delete ui; }

void Widget::onConnected() {
  ui->messageShoxEdit->appendPlainText("连接服务器成功！");
  ui->btnConnect->setText("断开");
  m_connected = true;
}

void Widget::onDisconnected() {
  ui->messageShoxEdit->appendPlainText("连接被断开！");
  ui->btnConnect->setText("连接");
  m_connected = false;
}

void Widget::onSendRead() {
  // 发送数据
  if (!m_sendQueue.empty()) {
    QString msg = m_sendQueue.dequeue();
    std::string strmsg = msg.toStdString();

   /**
    * @brief 发送数据
    * @param sockfd：要发送数据的套接字文件描述符。
    * @param buf：指向要发送的数据缓冲区的指针。
    * @param len：要发送的数据长度，以字节为单位。
    * @param flags：可选参数，通常置为0。可以通过设置flags来调整发送数据的行为。
    * @return 如果函数调用成功，返回值为已经发送的数据的字节数。
    *    如果返回-1，表示发送失败，可以通过 errno 变量获取错误代码
    * ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    */

    ssize_t sendLen = send(m_tcpsocket, strmsg.c_str(), strmsg.size(), 0);
    if (sendLen == -1) {
      return;
    }
  }

  // 接收数据
  char recvBuffer[1023];
  memset(recvBuffer, 0, sizeof(recvBuffer));

  /**
   * @brief 接收数据
   * @param sockfd：要接收数据的套接字文件描述符。
   * @param buf：指向接收数据的缓冲区的指针。
   * @param len：要接收的数据长度，以字节为单位。
   * @param flags：可选参数，通常置为0。可以通过设置flags来
   *        调整接收数据的行为。
   * @return 如果函数调用成功，返回值为已经接收的数据的字节数。
   *        如果返回0，表示连接已关闭。如果返回-1，表示接收失败，
   *        可以通过 errno 变量获取错误代码。
   *  ssize_t recv(int sockfd, void *buf, size_t len, int flags);
   */


  ssize_t recvLen = recv(m_tcpsocket, recvBuffer, sizeof(recvBuffer) - 1, 0);
  if (recvLen == -1) {
    return;
  }

  recvBuffer[recvLen] = 0;
  QString str = QString("收到数据: %1").arg(recvBuffer);
  ui->messageShoxEdit->appendPlainText(str);
}

void Widget::on_btnConnect_clicked() {
  if (!m_connected) {
    // 获取IP与端口
    QString ip = ui->lineEditIP->text();
    uint port = ui->lineEditPort->text().toUInt();
    // 创建套接字socket,设置通信类型为SOCK_STREAM 代表tcp
    m_tcpsocket = socket(AF_INET, SOCK_STREAM, 0);

    //  struct sockaddr_in {
    //      sa_family_t sin_family;     // 地址族(AF_INET)
    //      in_port_t sin_port;         // 端口号
    //      struct in_addr sin_addr;    // IPv4 地址
    //      char sin_zero[8];           // 填充字节，通常设置为 0
    //  };

    //  struct in_addr {
    //      in_addr_t s_addr;
    //  };

    //  typedef __uint32_t      in_addr_t;

    //  struct sockaddr_in 成员说明：
    //  sin_family：指定地址族，对于 IPv4 地址，应该设置为 AF_INET。
    //  sin_port：指定端口号，使用 in_port_t 类型表示，需要通过 htons()
    //  函数将端口号从主机字节序转换为网络字节序。 sin_addr：存储 IPv4
    //  地址信息，是一个 struct in_addr 类型的结构体。IPv4 地址应该通过
    //  inet_pton() 函数将字符串形式的 IP 地址转换为二进制形式。
    //  sin_zero：用于填充字节，通常设置为 0。
    struct sockaddr_in serverAddr;
    // 设置服务器地址和端口
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(port);
    inet_pton(AF_INET, ip.toStdString().c_str(), &(serverAddr.sin_addr));

    // 连接服务器

    /**
     * @brief 用于建立与远程服务器的连接
     * @param sockfd 要连接的套接字文件描述符。
     * @param addr 指向远程服务器地址的结构体指针。
     *         通常使用struct sockaddr_in结构体表示IPv4地址，
     *         或者struct sockaddr_in6结构体表示IPv6地址。
     * @param addrlen：远程服务器地址的长度，以字节为单位。
     * @return 如果函数调用成功，返回值为0。如果返回-1，表示连接
     *         失败，可以通过 errno 变量获取错误代码。
     */
     int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    if (::connect(m_tcpsocket, (struct sockaddr *)&serverAddr,
                  sizeof(serverAddr)) == -1) {
      ::close(m_tcpsocket);
      onDisconnected();
      return;
    }
    onConnected();

    // 将套接字设置为非阻塞模式,否则会阻塞界面
    int flags = fcntl(m_tcpsocket, F_GETFL, 0);
    if (flags < 0) {
      perror("fcntl F_GETFL");
      ::close(m_tcpsocket);
      return;
    }
    if (fcntl(m_tcpsocket, F_SETFL, flags | O_NONBLOCK) < 0) {
      perror("fcntl F_SETFL");
      ::close(m_tcpsocket);
      return;
    }

    m_timer.start();
  } else {
    // 断开与服务器的连接
    ::close(m_tcpsocket);
    onDisconnected();
    m_timer.stop();
  }
}

void Widget::on_btnSend_clicked() {
  QString msg = ui->messageEdit->text();
  // 给服务端发送数据
  m_sendQueue.enqueue(msg.toUtf8());
}
